#include "common.h"
#include "base64.h"

char *base64Encode(char *input, size_t inplen)
/* Use base64 to encode a string.  Returns one long encoded
 * string which need to be freeMem'd. Note: big-endian algorithm.
 * For some applications you may need to break the base64 output
 * of this function into lines no longer than 76 chars.
 */
{
  char b64[] = B64CHARS;
  int words = (inplen + 2) / 3;
  int remains = inplen % 3;
  char *result = (char *)needMem(4 * words + 1);
  size_t i = 0, j = 0;
  int word = 0;
  unsigned char *p = (unsigned char *)input;
  /* p must be unsigned char*,  because without "unsigned",
  sign extend messes up last group outputted
  when the value of the chars following last in input
  happens to be char 0x80 or higher */
  for (i = 1; i <= words; i++) {
    word = 0;
    word |= *p++;
    word <<= 8;
    word |= *p++;
    word <<= 8;
    word |= *p++;
    if (i == words && remains > 0) {
      word &= 0x00FFFF00;
      if (remains == 1)
        word &= 0x00FF0000;
    }
    result[j++] = b64[word >> 18 & 0x3F];
    result[j++] = b64[word >> 12 & 0x3F];
    result[j++] = b64[word >> 6 & 0x3F];
    result[j++] = b64[word & 0x3F];
  }
  result[j] = 0;
  if (remains > 0)
    result[j - 1] = '=';
  if (remains == 1)
    result[j - 2] = '=';
  return result;
}

boolean base64Validate(char *input)
/* Return true if input is valid base64.
 * Note that the input string is changed by
 * eraseWhiteSpace(). */
{
  size_t i = 0, l = 0;
  char *p = input;
  boolean validB64 = TRUE;

  /* remove whitespace which is unnecessary and  */
  eraseWhiteSpace(input);

  l = strlen(p);
  for (i = 0; i < l; i++) {
    char c = ' ';
    if (!strchr(B64CHARS, c = *p++)) {
      if (c != '=') {
        validB64 = FALSE;
        break;
      }
    }
  }
  if (l % 4)
    validB64 = FALSE;
  return validB64;
}

char *base64Decode(char *input, size_t *returnSize)
/* Use base64 to decode a string.  Return decoded
 * string which will be freeMem'd. Note: big-endian algorithm.
 * Call eraseWhiteSpace() and check for invalid input
 * before passing in input if needed.
 * Optionally set return size for use with binary data.
 */
{
  static int *map = NULL;
  char b64[] = B64CHARS;
  size_t inplen = strlen(input);
  int words = (inplen + 3) / 4;
  char *result = (char *)needMem(3 * words + 1);
  size_t i = 0, j = 0;
  int word = 0;
  char *p = input;

  if (!map) {
    int i = 0;
    map = needMem(256 * sizeof(int));
    for (i = 0; i < 256; ++i) {
      map[i] = 0;
    }
    for (i = 0; i < 64; ++i) {
      map[(int)b64[i]] = i;
    }
  }
  for (i = 0; i < words; i++) {
    word = 0;
    word |= map[(int)*p++];
    word <<= 6;
    word |= map[(int)*p++];
    word <<= 6;
    word |= map[(int)*p++];
    word <<= 6;
    word |= map[(int)*p++];
    result[j++] = word >> 16 & 0xFF;
    result[j++] = word >> 8 & 0xFF;
    result[j++] = word & 0xFF;
  }
  result[j] = 0;
  if (returnSize)
    *returnSize = j;

  return result;
}
